package com.training.service;

import com.training.entity.Course;
import com.training.entity.User;
import com.training.entity.Teacher;
import com.training.mapper.CourseMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.List;
import java.util.UUID;
import java.util.Objects;
import java.util.Collections;

@Service
public class CourseService {

    private static final Logger logger = LoggerFactory.getLogger(CourseService.class);

    private final CourseMapper courseMapper;
    private final TeacherService teacherService;
    private final StudentService studentService;

    @Value("${file.upload-base-dir}")
    private String uploadBaseDir;

    @Autowired
    public CourseService(CourseMapper courseMapper, TeacherService teacherService, StudentService studentService) {
        this.courseMapper = courseMapper;
        this.teacherService = teacherService;
        this.studentService = studentService;
    }

    public List<Course> getAllCourses() {
        logger.info("Fetching all non-deleted courses.");
        return courseMapper.findAllCourses();
    }

    public Course getCourseById(Long id) {
        if (id == null) {
            logger.warn("Attempted to get course with null ID.");
            return null;
        }
        logger.info("Fetching course by ID: {}", id);
        return courseMapper.findCourseById(id);
    }

    @Transactional
    public boolean addCourse(Course course, MultipartFile couPicFile) {
        if (course == null || course.getTeaId() == null || course.getCouName() == null || course.getCouName().trim().isEmpty()) {
            logger.warn("Attempted to add a course with missing essential information.");
            return false;
        }

        if (couPicFile != null && !couPicFile.isEmpty()) {
            try {
                String fileName = UUID.randomUUID().toString() + "_" + couPicFile.getOriginalFilename();
                Path uploadPath = Paths.get(uploadBaseDir + "/course_covers");
                Files.createDirectories(uploadPath);
                Path filePath = uploadPath.resolve(fileName);
                Files.copy(couPicFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
                course.setCouPic("/course_covers/" + fileName);
                logger.info("Course cover uploaded successfully: {}", fileName);
            } catch (IOException e) {
                logger.error("Failed to upload course cover for {}: {}", course.getCouName(), e.getMessage(), e);
                return false;
            }
        } else {
            course.setCouPic("/images/default-course.png");
        }

        logger.info("Adding new course: {}", course.getCouName());
        int affectedRows = courseMapper.addCourse(course);
        return affectedRows > 0;
    }

    /**
     * Updates an existing course.
     * This method now loads the existing course from the database first
     * to prevent null values for non-form fields.
     *
     * @param courseFromForm The course object with updated data from the form.
     * @param couPicFile The new cover image file (optional).
     * @return true if the update was successful, false otherwise.
     */
    @Transactional
    public boolean updateCourse(Course courseFromForm, MultipartFile couPicFile) {
        if (courseFromForm == null || courseFromForm.getId() == null) {
            logger.warn("Attempted to update a course with a missing ID.");
            return false;
        }

        // 1. Load the complete, original course object from the database.
        Course existingCourse = courseMapper.findCourseById(courseFromForm.getId());
        if (existingCourse == null) {
            logger.warn("Attempted to update a non-existent course with ID: {}", courseFromForm.getId());
            return false;
        }

        // 2. Copy the new values from the form into the existing object.
        // This ensures that fields not on the form (like like-count, collection-count, etc.)
        // retain their original values from the database.
        existingCourse.setCouName(courseFromForm.getCouName());
        existingCourse.setCouIntroduction(courseFromForm.getCouIntroduction());
        existingCourse.setCouPrice(courseFromForm.getCouPrice());
        existingCourse.setCouTypeId(courseFromForm.getCouTypeId());
        existingCourse.setCouParTypeId(courseFromForm.getCouParTypeId());

        // 3. Handle the file upload if a new image is provided.
        if (couPicFile != null && !couPicFile.isEmpty()) {
            try {
                // You might want to add logic here to delete the old image file
                String fileName = UUID.randomUUID().toString() + "_" + couPicFile.getOriginalFilename();
                Path uploadPath = Paths.get(uploadBaseDir, "course_covers");
                Files.createDirectories(uploadPath);
                Path filePath = uploadPath.resolve(fileName);
                Files.copy(couPicFile.getInputStream(), filePath, StandardCopyOption.REPLACE_EXISTING);
                existingCourse.setCouPic("/course_covers/" + fileName); // Set the new image path
                logger.info("Successfully uploaded new cover for course ID {}: {}", existingCourse.getId(), fileName);
            } catch (IOException e) {
                logger.error("Failed to update course cover for {}: {}", existingCourse.getCouName(), e.getMessage(), e);
                return false;
            }
        }

        // 4. Save the fully-populated, updated object back to the database.
        logger.info("Updating course ID: {}", existingCourse.getId());
        int affectedRows = courseMapper.updateCourse(existingCourse);
        return affectedRows > 0;
    }

    @Transactional
    public boolean deleteCourse(Long id, Long teacherId) {
        if (id == null || teacherId == null) {
            logger.warn("Attempted to delete course with null ID or teacher ID.");
            return false;
        }
        Course course = courseMapper.findCourseById(id);
        if (course == null) {
            logger.warn("Course with ID {} not found for deletion.", id);
            return false;
        }
        if (!Objects.equals(course.getTeaId(), teacherId)) {
            logger.warn("Unauthorized attempt to delete course ID {} by teacher ID {}. Course owned by teacher ID {}.", id, teacherId, course.getTeaId());
            throw new RuntimeException("您无权删除此课程。");
        }

        logger.info("Attempting to logically delete course ID {} by Teacher ID {}", id, teacherId);
        int affectedRows = courseMapper.deleteCourse(id, teacherId);
        return affectedRows > 0;
    }

    public List<Course> getCoursesByTeacherId(Long teacherId) {
        if (teacherId == null) {
            logger.warn("Attempted to retrieve courses with null teacherId.");
            return Collections.emptyList();
        }
        logger.info("Fetching courses for Teacher ID: {}", teacherId);
        return courseMapper.findCoursesByTeacherId(teacherId);
    }

    public List<Course> searchCoursesByTeacherIdAndName(Long teacherId, String couNameKeyword) {
        if (teacherId == null) {
            logger.warn("Attempted to search courses with null teacherId.");
            return Collections.emptyList();
        }
        if (couNameKeyword == null || couNameKeyword.trim().isEmpty()) {
            return courseMapper.findCoursesByTeacherId(teacherId);
        }
        logger.info("Searching courses for Teacher ID {} with keyword: '{}'", teacherId, couNameKeyword);
        return courseMapper.findByTeaIdAndCouNameContaining(teacherId, couNameKeyword);
    }

    @Transactional
    public boolean addCourseToClass(Long classId, Long courseId) {
        logger.info("Attempting to add course ID {} to class ID {}.", courseId, classId);

        Course existingCourse = courseMapper.findCourseById(courseId);
        if (existingCourse == null) {
            logger.warn("Could not add course {} to class {}: Course does not exist.", courseId, classId);
            return false;
        }

        if (courseMapper.isCourseInClass(classId, courseId) > 0) {
            logger.info("Course ID {} is already in class ID {}. No action needed.", courseId, classId);
            return true;
        }

        try {
            int affectedRows = courseMapper.addCourseToClass(classId, courseId);
            if (affectedRows > 0) {
                logger.info("Successfully added course ID {} to class ID {}.", courseId, classId);
                return true;
            } else {
                logger.warn("Failed to add course ID {} to class ID {}: Database operation was not successful.", courseId, classId);
                return false;
            }
        } catch (Exception e) {
            logger.error("An exception occurred while adding course ID {} to class ID {}: {}", courseId, classId, e.getMessage(), e);
            throw new RuntimeException("Failed to add course to class due to a system error.", e);
        }
    }

    public boolean hasStudentLikedCourse(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            return false;
        }
        return courseMapper.hasStudentLikedCourse(studentId, courseId) > 0;
    }

    @Transactional
    public boolean toggleCourseLike(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            throw new IllegalArgumentException("Student ID and Course ID cannot be null.");
        }

        boolean currentlyLiked = hasStudentLikedCourse(studentId, courseId);

        if (currentlyLiked) {
            courseMapper.deleteStudentCourseLike(studentId, courseId);
            courseMapper.decrementCourseLikeNum(courseId);
            logger.info("Student ID {} unliked course ID {}. Course like count decremented.", studentId, courseId);
            return false;
        } else {
            courseMapper.addStudentCourseLike(studentId, courseId);
            courseMapper.incrementCourseLikeNum(courseId);
            logger.info("Student ID {} liked course ID {}. Course like count incremented.", studentId, courseId);
            return true;
        }
    }

    public boolean hasStudentCollectedCourse(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            return false;
        }
        return courseMapper.hasStudentCollectedCourse(studentId, courseId) > 0;
    }

    @Transactional
    public boolean toggleCourseCollect(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            throw new IllegalArgumentException("Student ID and Course ID cannot be null.");
        }

        boolean currentlyCollected = hasStudentCollectedCourse(studentId, courseId);

        if (currentlyCollected) {
            courseMapper.deleteStudentCourseCollection(studentId, courseId);
            courseMapper.decrementCourseCollNum(courseId);
            logger.info("Student ID {} uncollected course ID {}. Course collection count decremented.", studentId, courseId);
            return false;
        } else {
            courseMapper.addStudentCourseCollection(studentId, courseId);
            courseMapper.incrementCourseCollNum(courseId);
            logger.info("Student ID {} collected course ID {}. Course collection count incremented.", studentId, courseId);
            return true;
        }
    }

    public boolean isStudentEnrolledInCourse(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            logger.warn("Attempted to check enrollment with null studentId or courseId.");
            return false;
        }
        logger.info("Checking if Student ID {} is enrolled in Course ID {}.", studentId, courseId);
        return courseMapper.checkIfStudentEnrolled(studentId, courseId) > 0;
    }

    @Transactional
    public boolean enrollStudentInCourse(Long studentId, Long courseId) {
        if (studentId == null || courseId == null) {
            logger.warn("Attempted to enroll student with null studentId or courseId.");
            throw new IllegalArgumentException("Student ID and Course ID cannot be null.");
        }

        Course existingCourse = courseMapper.findCourseById(courseId);
        if (existingCourse == null) {
            logger.warn("Enrollment failed for student ID {} in course ID {}: Course not found.", studentId, courseId);
            return false;
        }

        if (isStudentEnrolledInCourse(studentId, courseId)) {
            logger.info("Student ID {} is already enrolled in Course ID {}. No action needed.", studentId, courseId);
            return true;
        }

        try {
            logger.info("Attempting to enroll Student ID {} in Course ID {}.", studentId, courseId);
            int affectedRows = courseMapper.enrollCourse(studentId, courseId);
            if (affectedRows > 0) {
                logger.info("Successfully enrolled Student ID {} in Course ID {}.", studentId, courseId);
                return true;
            } else {
                logger.warn("Failed to enroll Student ID {} in Course ID {}: Database operation did not affect any rows.", studentId, courseId);
                return false;
            }
        } catch (Exception e) {
            logger.error("Error enrolling Student ID {} in Course ID {}: {}", studentId, courseId, e.getMessage(), e);
            throw new RuntimeException("Course enrollment failed due to a system error.", e);
        }
    }

    public List<Course> getCollectedCoursesByStudentId(Long studentId) {
        if (studentId == null) {
            logger.warn("Attempted to get collected courses with null student ID.");
            return Collections.emptyList();
        }
        logger.info("Fetching collected courses for Student ID: {}", studentId);
        return courseMapper.findCollectedCoursesByStudentId(studentId);
    }
}